// 'use strict';
var objectHandle = {

    /**
     * Object.defineProperty() 方法会直接在一个对象上定义一个新属性，或者修改一个对象的现有属性， 并返回这个对象。
     *
     * 语法：Object.defineProperty(obj, prop, descriptor)
     * obj 要在其上定义属性的对象。
     * prop 要定义或修改的属性的名称。
     * descriptor 将被定义或修改的属性描述符。
     *
     * 属性描述符有两种主要形式：数据描述符和存取描述符，描述符必须是这两种形式之一，不能同时是两者
     * 数据描述符是一个具有值的属性，该值可能是可写的，也可能不是可写的。
     * 存取描述符是由getter-setter函数对描述的属性。
     *
     * 数据描述符和存取描述符均具有以下可选键值：
     * configurable：当且仅当该属性的 configurable 为 true 时，该属性描述符才能够被改变，同时该属性也能从对应的对象上被删除。默认为 false。
     * enumerable：当且仅当该属性的enumerable为true时，该属性才能够出现在对象的枚举属性中。默认为 false。
     *
     * 数据描述符同时具有以下可选键值：
     * value：该属性对应的值。可以是任何有效的 JavaScript 值（数值，对象，函数等）。默认为 undefined
     * writable：当且仅当该属性的writable为true时，value才能被赋值运算符改变。默认为 false。
     *
     * 存取描述符同时具有以下可选键值：
     * get：一个给属性提供 getter 的方法，如果没有 getter 则为 undefined。当访问该属性时，该方法会被执行，方法执行时没有参数传入，但是会传入this对象（由于继承关系，这里的this并不一定是定义该属性的对象）。默认为 undefined。
     * set：一个给属性提供 setter 的方法，如果没有 setter 则为 undefined。当属性值修改时，触发执行该方法。该方法将接受唯一参数，即该属性新的参数值。默认为 undefined。
     */
    testDefineProperty: function () {

        // 创建一个新对象
        var o = {};

        // 在对象中添加一个属性与数据描述符的示例
        Object.defineProperty(o, "a", {
            value: 37,
            writable: false,
            enumerable: true,
            configurable: true
        });

        // 在对象中添加一个属性与存取描述符的示例
        var bValue;
        Object.defineProperty(o, "b", {
            get: function () {
                return bValue;
            },
            set: function (newValue) {
                bValue = newValue;
            },
            enumerable: true,
            configurable: true
        });

        o.b = 38;

        console.log(o);
        console.log(o.a);
        console.log(o.b);


        /**
         * 实际运用，监听对象中某个键的变化
         * 使用好处：AOP思想，使用切面，与老代码解耦
         */
        var data = {
            code: ''
        };
        var _code = null;
        Object.defineProperty(data, 'code', {
            get: function () {
                return _code;
            },
            set: function (newVal) {
                if (newVal !== _code) {
                    console.log("code form " + _code + " change to " + newVal);
                } else {
                    console.log("code no change, is " + _code);
                }
                _code = newVal;
            }
        });

        data.code = 1;
        data.code = 2;
        data.code = 2;
        data.code = 2;
        data.code = 3;
    },


    /**
     * Object.defineProperties() 方法直接在一个对象上定义新的属性或修改现有属性，并返回该对象。
     *
     * 语法：Object.defineProperties(obj, props)
     *
     * obj：在其上定义或修改属性的对象。
     * props：要定义其可枚举属性或修改的属性描述符的对象。对象中存在的属性描述符主要有两种：数据描述符和访问器描述符。描述符具有以下键：
     *
     * configurable：true 当且仅当该属性描述符的类型可以被改变并且该属性可以从对应对象中删除。默认为 false
     * enumerable：true 当且仅当在枚举相应对象上的属性时该属性显现。默认为 false
     * value：与属性关联的值。可以是任何有效的JavaScript值（数字，对象，函数等）。默认为 undefined.
     * writable：true当且仅当与该属性相关联的值可以用assignment operator改变时。默认为 false
     * get：作为该属性的 getter 函数，如果没有 getter 则为undefined。函数返回值将被用作属性的值。默认为 undefined
     * set：作为属性的 setter 函数，如果没有 setter 则为undefined。函数将仅接受参数赋值给该属性的新值。默认为 undefined
     */
    testDefineProperties: function () {
        var obj = {};
        Object.defineProperties(obj, {
            'property1': {
                value: true,
                writable: true
            },
            'property2': {
                value: 'Hello',
                writable: false
            }
        });

        console.log(obj);
    },


    /**
     * Object.seal()方法封闭一个对象，阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
     *
     * 语法：Object.seal(obj)
     *
     * Object.isSealed() 方法判断一个对象是否被密封。
     */
    testSeal: function () {
        var object1 = {
            property1: 1
        };

        Object.defineProperty(object1, "property1", {
            value: 2,
            writable: true // true，属性可改，false，属性不可改
        });

        Object.seal(object1);
        object1.property1 = 3;
        console.log(object1.property1); // 3

        delete object1.property1; // 不能删除已有字段
        console.log(object1.property1); // 3

        object1.property2 = 4; // 不能新增字段
        console.log(object1.property2); // undefined

        // 判断一个对象是否被密封
        console.log(Object.isSealed(object1));
    },

    /**
     * Object.preventExtensions()方法让一个对象变的不可扩展，也就是永远不能再添加新的属性。
     *
     * 语法：Object.preventExtensions(obj)
     *
     * Object.isExtensible() 方法判断一个对象是否是可扩展的（是否可以在它上面添加新的属性）。
     */
    testPreventExtensions: function () {
        // 字面量方式定义的对象默认是可扩展的.
        var empty = {};
        console.log(Object.isExtensible(empty)); //=== true

        Object.preventExtensions(empty);
        console.log(Object.isExtensible(empty)); //=== false


        var nonExtensible = {removable: true};
        Object.preventExtensions(nonExtensible);

        // 尝试新增属性，失败
        nonExtensible.new = 123;
        console.log(nonExtensible); // { removable: true }

        // 可删除原有属性
        delete nonExtensible.removable;
        console.log(nonExtensible); // {  }

        // 使用Object.defineProperty方法为一个不可扩展的对象添加新属性会抛出异常.
        // Object.defineProperty(nonExtensible, "new", { value: 8675309 }); // 抛出TypeError异常

    },


    /**
     * Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
     *
     * 语法：Object.assign(target, ...sources)
     * target：目标对象
     * sources：源对象
     * 返回值：目标对象
     *
     * 说明：对象浅拷贝
     */
    testAssign: function () {
        // 浅拷贝
        var obj = {a: 1};
        var copy = Object.assign({}, obj);
        obj.a = 2;
        console.log(JSON.stringify(copy)); // { a: 1} 

        var obj1 = {a: 0, b: {c: 0}};
        var obj2 = Object.assign({}, obj1);
        console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 0}} 

        obj1.a = 1;
        obj1.b.c = 2;
        console.log(JSON.stringify(obj1)); // { a: 1, b: { c: 2}} 
        console.log(JSON.stringify(obj2)); // { a: 0, b: { c: 2}} 

        // JSON 实现深拷贝
        var obj3 = {a: 0, b: {c: 0}};
        var obj4 = JSON.parse(JSON.stringify(obj3));
        obj3.a = 1;
        obj3.b.c = 2;
        console.log(JSON.stringify(obj3)); // { a: 1, b: { c: 2}}
        console.log(JSON.stringify(obj4)); // { a: 0, b: { c: 0}}

        // JQuery 实现深拷贝
        var obj5 = {a: 0, b: {c: 0}};
        var obj6 = $.extend(true, {}, obj5);
        obj5.a = 1;
        obj5.b.c = 2;
        console.log(JSON.stringify(obj5)); // { a: 1, b: { c: 2}}
        console.log(JSON.stringify(obj6)); // { a: 0, b: { c: 0}}
    },


    /**
     * Object.freeze() 方法可以冻结一个对象。
     *
     * 语法：Object.freeze(obj)
     * obj：要被冻结的对象。
     *
     * 说明：浅冻结
     *
     * Object.isFrozen()方法判断一个对象是否被冻结。
     */
    testFreeze: function () {

        // 冻结
        var obj = {prop: 42};
        Object.freeze(obj);
        obj.prop = 33; // 严格模式直接报错
        console.log(obj.prop); // 42

        // 判断一个对象是否被冻结
        console.log(Object.isFrozen(obj));

        // 浅冻结
        var obj1 = {internal: {}};
        Object.freeze(obj1);
        obj1.internal.a = 'anotherValue';
        console.log(obj1.internal.a); // anotherValue

        // 深冻结
        function deepFreeze(obj) {
            // 取回定义在obj上的属性名
            var propNames = Object.getOwnPropertyNames(obj);

            // 在冻结自身之前冻结属性
            propNames.forEach(function (name) {
                var prop = obj[name];

                // 如果prop是个对象，冻结它
                if (typeof prop == 'object' && prop !== null)
                    deepFreeze(prop);
            });

            // 冻结自身(no-op if already frozen)
            return Object.freeze(obj);
        }

        var obj2 = {internal: {}};
        deepFreeze(obj2);
        obj2.internal.a = 'anotherValue';
        console.log(obj2.internal.a); // undefined
    },

    /**
     * Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组，其排列与使用 for...in 循环遍历该对象时返回的顺序一致（区别在于 for-in 循环也枚举原型链中的属性）。
     *
     * 语法：Object.entries(obj)
     * obj：可以返回其可枚举属性的键值对的对象。
     * 返回值：给定对象自身可枚举属性的键值对数组。
     */
    testEntries: function () {
        var obj1 = {'a': 100, 'b': 2, 'c': 7};
        console.log(Object.entries(obj1)); // [ ['a', 100], ['b', 2], ['c', 7] ]

        // array like object with random key ordering
        var anObj = {100: 'a', 2: 'b', 7: 'c'};
        console.log(Object.entries(anObj)); // [ ['2', 'b'], ['7', 'c'], ['100', 'a'] ]

        // non-object argument will be coerced to an object
        console.log(Object.entries('foo')); // [ ['0', 'f'], ['1', 'o'], ['2', 'o'] ]

        // Or, using array extras
        var obj2 = {a: 5, b: 7, c: 9};
        Object.entries(obj2).forEach(([key, value]) => {
            console.log(`${key} ${value}`); // "a 5", "b 7", "c 9"
        });
    },


    /**
     * Object.fromEntries() 方法把键值对列表转换为一个对象。
     *
     * 语法：Object.fromEntries(iterable);
     * iterable：可迭代对象，类似 Array 、 Map 或者其它实现了可迭代协议的对象。
     * 返回值：一个由该迭代对象条目提供对应属性的新对象。
     *
     * 说明：Object.fromEntries() 是 Object.entries 的反转。
     */
    testFromEntries: function () {
        //  Map 转化为 Object
        var map = new Map([['foo', 'bar'], ['baz', 42]]);
        var obj1 = Object.fromEntries(map);
        console.log(obj1); // { foo: "bar", baz: 42 }

        // Array 转化为 Object
        var arr = [['0', 'a'], ['1', 'b'], ['2', 'c']];
        var obj2 = Object.fromEntries(arr);
        console.log(obj2); // { 0: "a", 1: "b", 2: "c" }

        // 对象转换
        var object1 = {a: 1, b: 2, c: 3};

        var object2 = Object.fromEntries(
            Object.entries(object1)
                .map(([key, val]) => [key, val * 2])
        );

        console.log(object2); // { a: 2, b: 4, c: 6 }
    },


    /**
     * Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组，数组中属性名的排列顺序和使用 for...in 循环遍历该对象时返回的顺序一致 。
     *
     * 语法：Object.keys(obj)
     */
    testKeys: function () {
        // simple array
        var arr = ['a', 'b', 'c'];
        console.log(Object.keys(arr)); // console: ['0', '1', '2']

        // array like object
        var obj = {0: 'a', 1: 'b', 2: 'c'};
        console.log(Object.keys(obj)); // console: ['0', '1', '2']

        // array like object with random key ordering
        var anObj = {100: 'a', 2: 'b', 7: 'c'};
        console.log(Object.keys(anObj)); // console: ['2', '7', '100']

    },


    /**
     * Object.values()方法返回一个给定对象自身的所有可枚举属性值的数组，值的顺序与使用for...in循环的顺序相同
     *
     * 语法：Object.values(obj)
     */
    testValues: function () {
        var obj1 = {foo: 'bar', baz: 42};
        console.log(Object.values(obj1)); // ['bar', 42]

        // array like object
        var obj2 = {0: 'a', 1: 'b', 2: 'c'};
        console.log(Object.values(obj2)); // ['a', 'b', 'c']

        // array like object with random key ordering
        // when we use numeric keys, the value returned in a numerical order according to the keys
        var an_obj = {100: 'a', 2: 'b', 7: 'c'};
        console.log(Object.values(an_obj)); // ['b', 'c', 'a']

        // non-object argument will be coerced to an object
        console.log(Object.values('foo')); // ['f', 'o', 'o']
    },


};